<template>
	<a-tabs
		class="route-tab"
		v-model:activeKey="activeKey"
		type="editable-card"
		@edit="onEdit"
		@tab-click="onTabClick"
		:hide-add="true"
	>
		<a-tab-pane v-for="pane in data" :key="pane.key" :tab="pane.title" :closable="pane.closable">
			<keep-alive>
				<component
					:is="pane.component"
					:ref="
						(el) => {
							setInstance(el, pane.key);
						}
					"
				/>
			</keep-alive>
		</a-tab-pane>
		<template #tabBarExtraContent v-if="refresh && data.length > 0">
			<a-button class="route-tab-refresh" @click="onRefresh">
				<template #icon>
					<ReloadOutlined class="" />
				</template>
			</a-button>
		</template>
	</a-tabs>
</template>

<script lang="ts">
import { defineComponent, ref, markRaw, watch, h } from "vue";
import { Tabs, Button, Spin, Result } from "ant-design-vue";
import { RouteLocationNormalized, RouteComponent, useRouter } from "vue-router";
import { ReloadOutlined } from "@ant-design/icons-vue";
type PaneComponent = RouteComponent & {
	refresh?: Function;
};
interface ErrorComponentOptions {
	// 当加载失败时 重新加载方法
	reload?: Function;
	// 当加载失败时 关闭当前页签方法
	close?: Function;
}
interface PaneData {
	key: string;
	title: string | null | undefined | symbol | unknown;
	closable: boolean;
	component: PaneComponent;
}
interface CompInstance {
	[name: string]: PaneComponent;
}
// 默认的`加载中...`组件
const getLoadingComponent = () => {
	return defineComponent({
		render() {
			return h(
				Spin,
				{
					tip: "加载中...",
				},
				{
					default() {
						return [
							h("div", {
								style: "min-height: 500px;",
							}),
						];
					},
				}
			);
		},
	});
};
const getErrorComponent = (options: ErrorComponentOptions) => {
	return defineComponent({
		render() {
			return h(
				Result,
				{
					status: "500",
					subTitle: "加载失败，请检查网络",
				},
				{
					extra() {
						return [
							h(
								Button,
								{
									type: "danger",
									onClick: () => {
										options.close && options.close();
									},
								},
								{
									default() {
										return "关闭";
									},
								}
							),
						];
					},
				}
			);
		},
	});
};
export default defineComponent({
	name: "MenuTabs",
	components: {
		"a-tabs": Tabs,
		"a-tab-pane": Tabs.TabPane,
		ReloadOutlined,
		AButton: Button,
	},
	emits: ["change"],
	props: {
		maxPaneNum: { type: Number, default: 10 },
		// 当超过最大数量时自动移除第一个pane
		autoRemovePan: { type: Boolean, default: true },
		refresh: { type: Boolean, default: true },
		// 只处理哪些路由
		restrictPath: {
			type: Array,
			default() {
				return [];
			},
		},
	},
	setup(props: any, ctx) {
		const activeKey = ref<string>("");
		const data = ref<PaneData[]>([]);
		const router = useRouter();
		const compInstance: CompInstance = {};
		watch(activeKey, (v) => {
			ctx.emit("change", v);
		});
		const onTabClick = (key: string) => {
			if (router.currentRoute.value.path !== key) {
				router.push(key);
			}
		};
		function onEdit(key: string) {
			let targetIndex: number | null = null;
			// 删除的是否已被选择
			let isCurSel = false;
			data.value.forEach((item, i) => {
				if (item.key === key) {
					targetIndex = i;
					isCurSel = item.key === activeKey.value;
					data.value.splice(i, 1);
					return false;
				}
			});
			if (targetIndex !== null && isCurSel && data.value.length > 0) {
				if (targetIndex > 0) {
					targetIndex--;
				}
				activeKey.value = data.value[targetIndex].key;
				onTabClick(activeKey.value);
			}
		}
		function makePaneData(route: RouteLocationNormalized, comp: PaneComponent): PaneData {
			const meta = route.meta || {};
			const paneData: PaneData = {
				key: route.path,
				title: meta.title || route.name,
				closable: true,
				component: markRaw(comp),
			};
			return paneData;
		}
		function addPane(route: RouteLocationNormalized, comp: PaneComponent) {
			const paneData = makePaneData(route, comp);
			data.value.push(paneData);
			activeKey.value = route.path;
		}
		function updataPane(route: RouteLocationNormalized, comp: PaneComponent) {
			const paneData = makePaneData(route, comp);
			data.value.forEach((item, i) => {
				if (item.key === route.path) {
					data.value[i] = paneData;
					return false;
				}
			});
		}
		return {
			activeKey,
			onEdit,
			data,
			add(route: RouteLocationNormalized) {
				if (props.restrictPath.length > 0 && props.restrictPath.indexOf(route.path) === -1) {
					return;
				}
				const value = data.value;
				for (const item of value) {
					if (item.key === route.path) {
						// 路由对应的TabPane已存在
						activeKey.value = item.key;
						return;
					}
				}
				if (value.length >= props.maxPaneNum) {
					if (props.autoRemovePan) {
						value.splice(0, 1);
					} else {
						return;
					}
				}
				const matched = route.matched;
				const compDefault = matched[matched.length - 1].components.default;
				// 不存在就push
				if (typeof compDefault === "object") {
					// 同步路由
					addPane(route, compDefault);
				} else if (typeof compDefault === "function") {
					// 异步路由
					addPane(route, getLoadingComponent());
					(compDefault as () => Promise<Record<string, PaneComponent>>)()
						.then((result) => {
							updataPane(route, result.default);
						})
						.catch(() => {
							updataPane(
								route,
								getErrorComponent({
									close: () => {
										onEdit(route.path);
									},
								})
							);
						});
				}
			},
			onTabClick,
			onRefresh() {
				const target = compInstance[activeKey.value];
				if (target && target.refresh) {
					target.refresh();
				}
			},
			setInstance(el: PaneComponent, key: string) {
				compInstance[key] = el;
			},
		};
	},
});
</script>
<style>
.route-tab .ant-tabs-extra-content {
	float: left !important;
}
.route-tab-refresh {
	margin: 0 3px;
	border: 0;
	box-shadow: none;
}
</style>
